<?php
namespace App;

/**
 * WebSocket客户端类
 * 用于向WebSocket服务器发送消息
 */
class WebSocketClient {
    private $socket;
    private $host;
    private $port;
    private $connected = false;
    private $maxRetries = 2;
    private $retryDelay = 500; // 毫秒
    
    /**
     * 构造函数
     * @param string $host WebSocket服务器主机
     * @param int $port WebSocket服务器端口
     */
    public function __construct($host = '127.0.0.1', $port = 8080) {
        $this->host = $host;
        $this->port = $port;
        
        // 添加调试信息
        error_log("WebSocketClient: 尝试连接到 {$this->host}:{$this->port}");
        
        // 检查主机名，如果是localhost或者IP地址，尝试使用127.0.0.1
        if (empty($this->host) || $this->host === 'localhost' || $this->host === '::1') {
            $this->host = '127.0.0.1';
            error_log("WebSocketClient: 主机名为localhost，使用127.0.0.1代替");
        }
        
        // 如果主机名包含端口号，分离它们
        if (strpos($this->host, ':') !== false && substr_count($this->host, ':') === 1) {
            list($this->host, $port) = explode(':', $this->host);
            $this->port = intval($port) ?: $this->port;
            error_log("WebSocketClient: 从主机名分离出端口号 - 主机:{$this->host} 端口:{$this->port}");
        }
        
        $this->connect();
    }
    
    /**
     * 连接到WebSocket服务器
     * @return bool 连接是否成功
     */
    private function connect() {
        try {
            error_log("WebSocketClient: 开始连接到 {$this->host}:{$this->port}");
            
            // 创建TCP/IP套接字
            $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            
            if ($this->socket === false) {
                $errorCode = socket_last_error();
                $errorMsg = socket_strerror($errorCode);
                throw new \Exception("socket_create() 失败: {$errorMsg} (错误码: {$errorCode})");
            }
            
            // 设置超时时间
            socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]);
            socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => 2, 'usec' => 0]);
            
            // 尝试使用不同的主机名连接
            $connectionHosts = [$this->host];
            
            // 如果不是IP地址，尝试IP解析
            if (!filter_var($this->host, FILTER_VALIDATE_IP)) {
                // 如果是服务器名，尝试解析为IP
                $ip = gethostbyname($this->host);
                if ($ip !== $this->host) {
                    $connectionHosts[] = $ip;
                    error_log("WebSocketClient: 解析主机名 {$this->host} 到IP {$ip}");
                }
                
                // 如果主机名是域名，尝试使用localhost
                if ($this->host !== 'localhost' && $this->host !== '127.0.0.1') {
                    $connectionHosts[] = '127.0.0.1';
                    $connectionHosts[] = 'localhost';
                }
            }
            
            // 尝试所有可能的主机名
            $connected = false;
            foreach ($connectionHosts as $host) {
                error_log("WebSocketClient: 尝试连接到 {$host}:{$this->port}");
                $result = @socket_connect($this->socket, $host, $this->port);
                
                if ($result !== false) {
                    $this->host = $host; // 更新为成功连接的主机
                    $connected = true;
                    error_log("WebSocketClient: 成功连接到 {$host}:{$this->port}");
                    break;
                }
                
                $errorCode = socket_last_error($this->socket);
                $errorMsg = socket_strerror($errorCode);
                error_log("WebSocketClient: 连接到 {$host}:{$this->port} 失败: {$errorMsg} (错误码: {$errorCode})");
            }
            
            if (!$connected) {
                throw new \Exception("无法连接到任何WebSocket服务器");
            }
            
            // WebSocket握手
            $key = base64_encode(openssl_random_pseudo_bytes(16));
            $headers = "GET / HTTP/1.1\r\n";
            $headers .= "Host: {$this->host}:{$this->port}\r\n";
            $headers .= "Upgrade: websocket\r\n";
            $headers .= "Connection: Upgrade\r\n";
            $headers .= "Sec-WebSocket-Key: {$key}\r\n";
            $headers .= "Sec-WebSocket-Version: 13\r\n\r\n";
            
            $sent = socket_write($this->socket, $headers, strlen($headers));
            if ($sent === false) {
                $errorCode = socket_last_error($this->socket);
                $errorMsg = socket_strerror($errorCode);
                throw new \Exception("发送WebSocket握手失败: {$errorMsg} (错误码: {$errorCode})");
            }
            
            // 读取服务器响应
            $response = socket_read($this->socket, 2048);
            
            if (!$response) {
                $errorCode = socket_last_error($this->socket);
                $errorMsg = socket_strerror($errorCode);
                throw new \Exception("无法读取握手响应: {$errorMsg} (错误码: {$errorCode})");
            }
            
            // 检查握手是否成功
            if (strpos($response, "HTTP/1.1 101") === false) {
                throw new \Exception("WebSocket握手失败: " . substr($response, 0, 100) . "...");
            }
            
            $this->connected = true;
            error_log("WebSocketClient: WebSocket握手成功，连接已建立");
            return true;
        } catch (\Exception $e) {
            error_log("WebSocketClient ERROR: " . $e->getMessage());
            
            if ($this->socket) {
                socket_close($this->socket);
                $this->socket = null;
            }
            
            $this->connected = false;
            return false;
        }
    }
    
    /**
     * 发送消息到WebSocket服务器
     * @param string $message 要发送的消息
     * @return bool 是否发送成功
     */
    public function send($message, $retries = 0) {
        // 如果未连接，尝试重新连接
        if (!$this->connected && $retries < $this->maxRetries) {
            if (!$this->connect()) {
                // 如果未达到最大重试次数，等待后重试
                usleep($this->retryDelay * 1000);
                return $this->send($message, $retries + 1);
            }
        }
        
        if (!$this->connected) {
            error_log("WebSocket未连接，无法发送消息");
            return false;
        }
        
        try {
            // 将消息打包为WebSocket帧
            $frame = $this->hybi10Encode($message);
            
            // 发送帧
            $sent = socket_write($this->socket, $frame, strlen($frame));
            
            if ($sent === false) {
                throw new \Exception("socket_write() 失败: " . socket_strerror(socket_last_error($this->socket)));
            }
            
            return true;
        } catch (\Exception $e) {
            error_log("WebSocket发送错误: " . $e->getMessage());
            
            // 关闭连接
            $this->close();
            
            // 如果未达到最大重试次数，重新连接后重试
            if ($retries < $this->maxRetries) {
                usleep($this->retryDelay * 1000);
                return $this->send($message, $retries + 1);
            }
            
            return false;
        }
    }
    
    /**
     * 关闭WebSocket连接
     */
    public function close() {
        if ($this->socket) {
            // 发送关闭帧
            if ($this->connected) {
                try {
                    $closeFrame = $this->hybi10EncodeClose();
                    socket_write($this->socket, $closeFrame, strlen($closeFrame));
                } catch (\Exception $e) {
                    error_log("发送关闭帧错误: " . $e->getMessage());
                }
            }
            
            socket_close($this->socket);
            $this->socket = null;
            $this->connected = false;
        }
    }
    
    /**
     * 发送订单状态更新消息
     * @param array $order 订单信息
     * @return bool 是否发送成功
     */
    public function sendOrderStatusUpdate($order) {
        $message = [
            'type' => 'order_status',
            'order' => $order
        ];
        
        // 确保订单信息有效
        if (empty($order) || !isset($order['id'])) {
            error_log("无效的订单信息，无法发送状态更新");
            return false;
        }
        
        // 获取完整订单信息
        try {
            $order_items = \Database::fetchAll(
                "SELECT * FROM order_items WHERE order_id = ?", 
                [$order['id']]
            );
            
            // 添加订单项信息
            $message['order']['items'] = $order_items;
            
            // 注意进行JSON转换前进行编码检查
            $json = json_encode($message, JSON_UNESCAPED_UNICODE);
            
            if ($json === false) {
                throw new \Exception("JSON编码失败: " . json_last_error_msg());
            }
            
            // 添加调试信息
            error_log("发送订单状态更新WebSocket消息: 订单ID=" . $order['id'] . ", 状态=" . $order['status']);
            
            // 发送消息
            return $this->send($json);
        } catch (\Exception $e) {
            error_log("发送订单状态更新WebSocket消息时发生错误: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * 发送订单项状态更新消息
     * @param array $item 订单项信息
     * @param int $order_id 订单ID
     * @return bool 是否发送成功
     */
    public function sendOrderItemStatusUpdate($item, $order_id) {
        if (empty($item) || !isset($item['id']) || !$order_id) {
            error_log("无效的订单项信息，无法发送状态更新");
            return false;
        }
        
        error_log("准备发送订单项状态更新: 项目ID={$item['id']}, 订单ID={$order_id}, 状态={$item['status']}");
        
        $message = [
            'type' => 'order_item_status',
            'item' => $item,
            'order_id' => $order_id
        ];
        
        try {
            // 获取订单信息，用于给会员客户端提供更多信息
            $order = \Database::fetch("SELECT * FROM orders WHERE id = ?", [$order_id]);
            if ($order) {
                $message['order'] = $order;
            }
            
            $json = json_encode($message, JSON_UNESCAPED_UNICODE);
            if ($json === false) {
                throw new \Exception("JSON编码失败: " . json_last_error_msg());
            }
            
            // 添加调试信息
            error_log("发送订单项状态更新WebSocket消息: 订单项ID=" . $item['id'] . ", 订单ID=" . $order_id . ", 状态=" . $item['status']);
            error_log("WebSocket消息内容: " . substr($json, 0, 200) . (strlen($json) > 200 ? '...' : ''));
            
            $result = $this->send($json);
            error_log("WebSocket消息发送结果: " . ($result ? "成功" : "失败"));
            return $result;
        } catch (\Exception $e) {
            error_log("发送订单项状态更新WebSocket消息时发生错误: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * 发送新订单消息
     * @param array $order 订单信息
     * @return bool 是否发送成功
     */
    public function sendNewOrder($order) {
        $message = [
            'type' => 'order_new',
            'order' => $order
        ];
        
        // 确保订单信息有效
        if (empty($order) || !isset($order['id'])) {
            error_log("无效的订单信息，无法发送新订单通知");
            return false;
        }
        
        // 获取完整订单信息
        try {
            $order_items = \Database::fetchAll(
                "SELECT * FROM order_items WHERE order_id = ?", 
                [$order['id']]
            );
            
            // 添加订单项信息
            $message['order']['items'] = $order_items;
            
            // 检查会员信息
            if (!empty($order['user_id'])) {
                $member = \Database::fetch(
                    "SELECT id, name, phone FROM users WHERE id = ?", 
                    [$order['user_id']]
                );
                if ($member) {
                    $message['order']['member'] = $member;
                }
            }
            
            // 注意进行JSON转换前进行编码检查
            $json = json_encode($message, JSON_UNESCAPED_UNICODE);
            
            if ($json === false) {
                throw new \Exception("JSON编码失败: " . json_last_error_msg());
            }
            
            return $this->send($json);
        } catch (\Exception $e) {
            error_log("准备新订单消息失败: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * 对消息进行hybi10格式的WebSocket帧编码
     * @param string $payload 要编码的消息
     * @param string $type 消息类型，默认为文本
     * @param bool $masked 是否掩码，客户端发送需要设为true
     * @return string 编码后的WebSocket帧
     */
    private function hybi10Encode($payload, $type = 'text', $masked = true) {
        $frameHead = [];
        $payloadLength = strlen($payload);
        
        // 第一个字节
        $frameHead[0] = (($type === 'text') ? 0x81 : 0x82);
        
        // 第二个字节开始，处理payload长度
        if ($payloadLength <= 125) {
            // 7位长度
            $frameHead[1] = $payloadLength;
        } elseif ($payloadLength <= 65535) {
            // 16位长度
            $frameHead[1] = 126;
            $frameHead[2] = ($payloadLength >> 8) & 0xFF;
            $frameHead[3] = $payloadLength & 0xFF;
        } else {
            // 64位长度
            $frameHead[1] = 127;
            for ($i = 9; $i > 1; $i--) {
                $frameHead[$i] = $payloadLength & 0xFF;
                $payloadLength >>= 8;
            }
        }
        
        // 第二个字节的最高位设置为1表示使用掩码
        if ($masked) {
            $frameHead[1] |= 0x80;
        }
        
        // 转换为二进制字符串
        $frame = '';
        foreach ($frameHead as $byte) {
            $frame .= chr($byte);
        }
        
        if ($masked) {
            // 生成4字节掩码
            $mask = [];
            for ($i = 0; $i < 4; $i++) {
                $mask[$i] = rand(0, 255);
                $frame .= chr($mask[$i]);
            }
            
            // 对消息进行掩码处理
            $payloadEncoded = '';
            for ($i = 0; $i < strlen($payload); $i++) {
                $payloadEncoded .= chr(ord($payload[$i]) ^ $mask[$i % 4]);
            }
            
            $frame .= $payloadEncoded;
        } else {
            $frame .= $payload;
        }
        
        return $frame;
    }
    
    /**
     * 编码WebSocket关闭帧
     * @return string 关闭帧
     */
    private function hybi10EncodeClose() {
        $closeStatus = 1000; // 正常关闭
        
        $frame = chr(0x88); // 二进制 10001000，表示关闭帧
        $frame .= chr(0x82); // 使用掩码，2字节长度
        
        // 掩码
        $mask = [];
        for ($i = 0; $i < 4; $i++) {
            $mask[$i] = rand(0, 255);
            $frame .= chr($mask[$i]);
        }
        
        // 关闭代码
        $encoded = chr(($closeStatus >> 8) & 0xFF) ^ chr($mask[0]);
        $encoded .= chr($closeStatus & 0xFF) ^ chr($mask[1]);
        
        $frame .= $encoded;
        
        return $frame;
    }
    
    /**
     * 析构函数，确保套接字被关闭
     */
    public function __destruct() {
        $this->close();
    }
} 